package com.dobbinsoft.iotechn.ability.openapi;

import com.alibaba.fastjson.JSONObject;
import com.dobbinsoft.iotechn.ability.annotation.DeviceOpenApi;
import com.dobbinsoft.iotechn.ability.annotation.DeviceOpenApiMethod;
import com.dobbinsoft.iotechn.ability.enums.DeviceOpenApiResultType;
import com.dobbinsoft.iotechn.ability.model.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.util.*;
import java.util.stream.Collectors;

/**
 * Description:
 * User: rize
 * Date: 2020/7/6
 * Time: 20:40
 */
@Component
public class OpenApiManagerImpl implements IOpenApiManager, InitializingBean {

    private static final Logger logger = LoggerFactory.getLogger(OpenApiManagerImpl.class);

    private Map<String, Map<String, Method>> openApiMethodCache = new HashMap<>();

    private Set<DeviceOpenApiDocument> openApiSet = new TreeSet<>();

    @Autowired
    private ApplicationContext applicationContext;

    @Override
    public void afterPropertiesSet() throws Exception {
        List<Class> classList = new LinkedList<>();
        String[] beanArray = applicationContext.getBeanNamesForAnnotation(DeviceOpenApi.class);
        for (String deviceApiBean : beanArray) {
            Object bean = applicationContext.getBean(deviceApiBean);
            classList.add(bean.getClass());
        }
        for (Class clazz : classList) {
            this.registerDeviceOpenApi(clazz);
        }
    }

    @Override
    public DeviceOpenApiResultModel invoke(String _gp, String _mt, String invokeId, AbilityContext abilityContext) {
        DeviceOpenApiResultModel resultModel = new DeviceOpenApiResultModel();
        Map<String, Method> groupOpenApi = openApiMethodCache.get(_gp);
        if (groupOpenApi != null) {
            Method method = groupOpenApi.get(_mt);
            if (method != null) {
                try {
                    Object res = method.invoke(applicationContext.getBean(method.getDeclaringClass()), abilityContext);
                    resultModel.setCode(DeviceOpenApiResultType.SUCCESS.getCode());
                    resultModel.setRes(res);
                    return resultModel;
                } catch (Exception e) {
                    logger.error("[DeviceApi] 异常", e);
                    resultModel.setCode(DeviceOpenApiResultType.EXCEPTION.getCode());
                    Map<String, String> res = new HashMap<>();
                    res.put("error", e.getMessage());
                    resultModel.setRes(res);
                    return resultModel;
                }
            }
        }
        resultModel.setCode(DeviceOpenApiResultType.API_NOT_EXIST.getCode());
        Map<String, String> res = new HashMap<>();
        res.put("error", "Api not exist!");
        resultModel.setRes(res);
        return resultModel;
    }

    @Override
    public Set<DeviceOpenApiDocument> listDoc() {
        return this.openApiSet;
    }

    public void registerDeviceOpenApi(Class<?> apiClazz) {
        DeviceOpenApi deviceOpenApi = apiClazz.getDeclaredAnnotation(DeviceOpenApi.class);
        if (deviceOpenApi != null) {
            Map<String, Method> groupApiMap = openApiMethodCache.get(deviceOpenApi.group());
            if (groupApiMap == null) {
                groupApiMap = new HashMap<>();
                openApiMethodCache.put(deviceOpenApi.group(), groupApiMap);
            }
            Method[] methods = apiClazz.getMethods();
            // 获取所有方法
            for (Method method : methods) {
                Class<?>[] parameterTypes = method.getParameterTypes();
                Class<?> returnType = method.getReturnType();
                // 若两者
                DeviceOpenApiMethod deviceOpenApiMethod = method.getAnnotation(DeviceOpenApiMethod.class);
                if (deviceOpenApiMethod != null) {
                    if (parameterTypes.length == 1 && parameterTypes[0] == AbilityContext.class && returnType == Object.class) {
                        DeviceOpenApiDocument document = new DeviceOpenApiDocument();
                        String docName = deviceOpenApi.group() + "." + method.getName();
                        document.setName(docName);
                        document.setDescription(deviceOpenApi.description() + "--" + deviceOpenApiMethod.description());
                        List<DeviceOpenApiParamDocument> paramDocumentList = Arrays.stream(deviceOpenApiMethod.paramDescription()).map(item -> {
                            DeviceOpenApiParamDocument paramDocument = new DeviceOpenApiParamDocument();
                            paramDocument.setDescription(item.description());
                            paramDocument.setName(item.name());
                            paramDocument.setExample(item.example());
                            return paramDocument;
                        }).collect(Collectors.toList());
                        List<DeviceOpenApiReturnDocument> returnDocumentList = Arrays.stream(deviceOpenApiMethod.retDescription()).map(item -> {
                            DeviceOpenApiReturnDocument returnDocument = new DeviceOpenApiReturnDocument();
                            returnDocument.setName(item.name());
                            returnDocument.setDescription(item.description());
                            returnDocument.setExample(item.example());
                            return returnDocument;
                        }).collect(Collectors.toList());
                        document.setParamDocumentList(paramDocumentList);
                        document.setReturnDocumentList(returnDocumentList);
                        groupApiMap.put(method.getName(), method);
                        openApiSet.add(document);
                    } else {
                        throw new RuntimeException("错误的方法签名");
                    }
                }
            }
        }
    }
}
